package image;

import static math.math.*;
import math.vec2;
import math.vec2i;
import static math.vec4.*;
import math.vec4;

public class Image4 extends Image
{
    
    private vec4[] mData;
    
    
    public Image4(vec2i aSize)
    {
        // Init the Image size:
        super(aSize);
        // Init the array of data:
        int numCells = aSize.x * aSize.y;
        this.mData = new vec4[numCells];
        for (int i = 0; i < numCells; i++)
        {
            this.mData[i] = new vec4();
        }
    }
    
    
    public Image copy(
            vec2i aStart, 
            vec2i aSize)
    {
        Image4 copy = new Image4(aSize);
        Image4.copy(this, aStart, copy, vec2i.ZERO, aSize);
        return copy;
    }
    
    
    /** Writes a datum to this Image. */
    public void write(
            vec2i aPixel, 
            vec4 aDatum)
    {
        this.write(this.toIntIndex(aPixel), aDatum);
    }
    
    /** Writes a datum to this Image. */
    public void write(
            int aIndex, 
            vec4 aDatum)
    {
        this.mData[aIndex].set(aDatum);
    }
    
    
    /** Returns a modifiable datum from the Image. Changing the datum will change the Image. */
    public vec4 read(vec2i aPixel)
    {
        return this.read(this.toIntIndex(aPixel));
    }
    
    /** Returns a modifiable datum from the Image. Changing the datum will change the Image. */
    public vec4 read(int aIndex)
    {
        return this.mData[aIndex];
    }
    
    /** Returns a copy of the datum, so no changes to the Image will later occur. */
    public vec4 readCopy(vec2i aPixel)
    {
        return this.readCopy(this.toIntIndex(aPixel));
    }
    
    /** Returns a copy of the datum, so no changes to the Image will later occur. */
    public vec4 readCopy(int aIndex)
    {
        return this.mData[aIndex].copy();
    }
    
    
    public vec4 lookupNN(vec2 uv)
    {
        final vec2 imgSize = new vec2(this.mSize);
        imgSize.sub(1.0);
        final vec2 st = vec2.mul(uv, imgSize);
        st.add(0.5);
        vec2i texel = new vec2i(st);
        return this.readCopy(texel);
    }
    
    
    public static void writeAll(
            Image4 aImage, 
            vec4 aColor)
    {
        for (vec4 datum : aImage.mData)
        {
            datum.set(aColor);
        }
    }
    
    public static void scaleAll(
            Image4 aImage, 
            vec4 aScale)
    {
        for (vec4 datum : aImage.mData)
        {
            datum.mul(aScale);
        }
    }
    
    public static void clampAll(
            Image4 aImage, 
            double aBoundsLo, 
            double aBoundsHi)
    {
        for (vec4 datum : aImage.mData)
        {
            datum.clamp(aBoundsLo, aBoundsHi);
        }
    }
    
    public static void powAll(
            Image4 aImage, 
            double aExp)
    {
        for (vec4 datum : aImage.mData)
        {
            datum.pow(aExp);
        }
    }
    
    public static void mapAll(
            Image4 aImage, 
            double aBoundsLo, 
            double aBoundsHi)
    {
        vec2 minmax = new vec2();
        for (vec4 datum : aImage.mData)
        {
            double datumMax = max(max(datum.x, datum.y), max(datum.z, datum.w));
            double datumMin = min(min(datum.x, datum.y), min(datum.z, datum.w));
            if (datumMin < minmax.x)
            {
                minmax.x = datumMin;
            }
            if (datumMax > minmax.y)
            {
                minmax.y = datumMax;
            }
        }
        vec4 min = new vec4(minmax.x);
        vec4 max = new vec4(minmax.y);
        vec4 rwidth = div(1.0, sub(max, min));
        vec4 newWidth = sub(aBoundsHi, aBoundsLo);
        for (vec4 datum : aImage.mData)
        {
            vec4 norm = mul(sub(datum, min), rwidth);
            vec4 mapped = add(aBoundsLo, mul(norm, newWidth));
            datum.set(mapped);
        }
    }
    
    public static void copy(
            Image4 aSRC, 
            vec2i aSRCStart, 
            Image4 aDST, 
            vec2i aDSTStart, 
            vec2i aSize)
    {
        vec2i locSRC = new vec2i();
        vec2i locDST = new vec2i();
        for (int j = 0; j < aSize.y; j++)
        {
            locSRC.y = j + aSRCStart.y;
            locDST.y = j + aDSTStart.y;
            for (int i = 0; i < aSize.x; i++)
            {
                locSRC.x = i + aSRCStart.x;
                locDST.x = i + aDSTStart.x;
                
                vec4 datum = aSRC.read(locSRC);
                aDST.write(locDST, datum);
            }
        }
    }
    
    public static void flipAroundHoriz(Image4 aImage)
    {
        int numToFlip = aImage.mSize.y / 2;
        vec2i pixel = new vec2i();
        vec2i other = new vec2i();
        for (int y = 0; y < numToFlip; y++)
        {
            int yOther = aImage.mSize.y - y - 1;
            for (int x = 0; x < aImage.mSize.x; x++)
            {
                pixel.set(x, y);
                other.set(x, yOther);
                int ind0 = aImage.toIntIndex(pixel);
                int ind1 = aImage.toIntIndex(other);
                vec4 tSwap = aImage.mData[ind0];
                aImage.mData[ind0] = aImage.mData[ind1];
                aImage.mData[ind1] = tSwap;
            }
        }
    }
    
    public static void flipAroundVert(Image4 aImage)
    {
        int numToFlip = aImage.mSize.x / 2;
        vec2i pixel = new vec2i();
        vec2i other = new vec2i();
        for (int x = 0; x < numToFlip; x++)
        {
            int xOther = aImage.mSize.x - x - 1;
            for (int y = 0; y < aImage.mSize.y; y++)
            {
                pixel.set(x, y);
                other.set(xOther, y);
                int ind0 = aImage.toIntIndex(pixel);
                int ind1 = aImage.toIntIndex(other);
                vec4 tSwap = aImage.mData[ind0];
                aImage.mData[ind0] = aImage.mData[ind1];
                aImage.mData[ind1] = tSwap;
            }
        }
    }
    
}
